package mud.unit;

import mud.Battle;
import mud.Combatable;
import mud.Entity;
import mud.Exit;
import mud.Interactable;
import mud.Observer;
import mud.Race;
import mud.Room;
import mud.events.AttackEvent;
import mud.events.EnterEvent;
import mud.events.ExitEvent;
import mud.events.LookEvent;

/** An entity that is capable of performing actions */
public abstract class Character extends Entity 
    implements Observer, Combatable {

    /** the name of the unit */
	private String name;
    
	/** the description of the way this unit looks */
	private String appearance;

	/** the health of the unit */
    private long health;

    /** the strength of the unit */
    private long strength;

    /** the species of the unit */
    private Race race;

    /** the current battle, if any */
    private Battle battle;

    /** the current room of the entity */
    private Room room;

    /** the action that the unit will make on its next turn */
    private Action nextAction;

	/**
	 * Constructor
	 */
	public Character() {
	    this.addObserver(this);
	}
	
    @Override
    public void defend(Combatable attacker) {
        long damage = attacker.getStrength();
        this.health -= damage;
        fireEvent(new AttackEvent(attacker, this, damage));
    }

    @Override
    public long getHealth() {
        return health;
    }

    @Override
    public long getStrength() {
        return strength;
    }

    @Override
    public String getAppearance() {
        return appearance;
    }

    /**
     * @param appearance the description of the way this unit looks
     */
    public void setAppearance(String appearance) {
        this.appearance = appearance;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "["
        	+ "name=" + name + "]";
    }

	@Override
    public String getName() {
		return name;
	}

    /**
     * @param name the name of the unit
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @param race the species of the unit
     */
    public void setRace(Race race) {
        this.race = race;
        this.health = race.getHealth();
        this.strength = race.getStrength();
    }

    @Override
    public String getType() {
        return race.getName();
    }
    
    @Override
    public Battle getBattle() {
        return battle;
    }

    @Override
    public void setBattle(Battle battle) {
        this.battle = battle;
    }

    @Override
    public boolean isInCombat() {
        return this.battle != null;
    }
    
    /**
     * Causes the unit to enter the given room
     * @param entrance the room to enter
     */
    public void enter(Exit entrance) {
        room = entrance.getRoom();
        room.addOccupant(this);
        fireEvent(new EnterEvent(this, room));
        look(room);
    }

    /**
     * @return the current room of the unit
     */
    public Room getRoom() {
        return room;
    }

    /**
     * Causes the unit to perform the given action either immediately or on its
     * next turn if in combat
     * @param action the action to perform
     */
    public final void performAction(Action action) {
        if (action instanceof AttackAction) {
            nextAction = action;
            this.engage(((AttackAction)action).getTargets());
            battle.takeTurns();
        } else if (this.isInCombat() && action.isTurnConsumed()) {
            nextAction = action;
            battle.takeTurns();
        } else {
            action.perform();
        }
    }
    
    /**
     * Causes the unit to attack a given target
     * @param target the entity to attack
     */
    protected void attack(Combatable target) {
        target.defend(this);
    }

    /**
     * Causes the unit to move through the given exit
     * @param exit the exit to take
     */
    protected void exit(Exit exit) {
        fireEvent(new ExitEvent(this, exit));
        getRoom().removeOccupant(this);
        room = null;
        enter(exit.getPeer());
    }

    /**
     * Causes the unit to look at the given entity
     * @param entity the entity at which to look
     */
    protected void look(Interactable entity) {
        fireEvent(new LookEvent(this, entity));
    }

    /**
     * Synchronizes combat with another combatant, if needed
     * @param targets the units with which combat is being engaged
     */
    protected void engage(Combatable ... targets) {
        for (Combatable target : targets) {
            if (this.isInCombat()) {
                if (!battle.equals(target.getBattle())) {
                    battle.addCombatant(target);
                }
            } else {
                if (target.isInCombat()) {
                    target.getBattle().addCombatant(this);
                } else {
                    battle = new Battle(this, target);
                }
            }
        }
    }

    /**
     * @return the action that the unit will make on its next turn
     */
    @Override
    public Action getNextAction() {
        assert isInCombat();
        Action tempAction = nextAction;
        nextAction = null;
        return tempAction;
    }
}
